﻿namespace Offy.Itdev.BuyIn.Intranet.Core.Repositories
{
    using System;
    using System.Collections.Generic;
    using Microsoft.SharePoint;
    using System.Reflection;
    using System.Linq;
    using DTO;

    public abstract class BaseDao<T> : IDao<T> where T : IBaseDTO, new()
    {
        protected readonly string ListName = string.Empty;
        protected readonly SPWeb SPWeb = null;

        protected BaseDao() { }

        protected BaseDao(SPWeb spWeb, string listName)
        {
            this.SPWeb = spWeb;
            this.ListName = listName;
        }

        public T GetById(int id)
        {
            T entity = default(T);
            SPListItem item = this.GetSPListItem(id);
            entity = this.MapDTO(item);
            return entity;
        }

        public T Get(string query)
        {
            T entity = default(T);
            var spQuery = new SPQuery()
                          {
                              Query = query,
                              RowLimit = 1
                          };
            SPListItemCollection itemCollection = this.GetSPList().GetItems(spQuery);
            if (itemCollection != null)
            {
                entity = MapDTO(itemCollection[0]);
            }
            return entity;
        }

        public int GetTotalRecord(string query)
        {
            var spQuery = new SPQuery()
            {
                Query = query,
                ViewFields = "<FieldRef Name='Id'/>"
            };
            return this.GetSPList().GetItems(spQuery).Count;
        }

        public int GetTotalRecord(string query, string sortBy, bool isAscending, string recursive)
        {
            var spQuery = new SPQuery()
            {
                Query = query + "<OrderBy><FieldRef Name=\"" + sortBy + "\" Ascending=\"" + isAscending + "\" /></OrderBy>",
                ViewFields = "<FieldRef Name='Id'/>"
            };
            if (!string.IsNullOrEmpty(recursive)) spQuery.ViewAttributes = recursive;
            return this.GetSPList().GetItems(spQuery).Count;
        }

        public IList<T> GetList(string query)
        {
            IList<T> list = new List<T>();
            var spQuery = new SPQuery(){ Query = query };
            SPListItemCollection itemCollection = this.GetSPList().GetItems(spQuery);
            foreach (SPListItem item in itemCollection)
            {
                var entity = MapDTO(item);
                list.Add(entity);
            }
            return list;
        }

        public IList<T> GetList(string query, int pageIndex, int pageSize, string recursive)
        {
            IList<T> list = new List<T>();
            SPListItemCollection itemCollection = this.GetSPListItemCollection(query, recursive, recursive, pageIndex, pageSize);
            foreach (SPListItem item in itemCollection)
            {
                var entity = MapDTO(item);
                list.Add(entity);
            }
            return list;
        }

        public int Add(T entity)
        {
            bool webStatus = this.SetAllowUnsafeUpdates();
            var fields = this.GatherParameters(entity);
            var list = this.GetSPList();
            SPListItem item = list.AddItem();
            foreach (var key in fields.Keys)
            {
                if (!list.Fields[key].ReadOnlyField)
                {
                    item[key] = fields[key];
                }
            }
            item.Update();
            this.UnSetAllowUnsafeUpdates(webStatus);
            return item.ID;
        }

        public void Update(T entity)
        {
            bool webStatus = this.SetAllowUnsafeUpdates();
            var fields = this.GatherParameters(entity);
            SPListItem item = this.GetSPListItem(entity.ID);
            foreach (var key in fields.Keys)
            {
                item[key] = fields[key];
            }
            item.Update();
            this.UnSetAllowUnsafeUpdates(webStatus);
        }

        public void Delete(T entity)
        {
            bool webStatus = this.SetAllowUnsafeUpdates();
            SPListItem item = this.GetSPListItem(entity.ID);
            item.Delete();
            this.GetSPList().Update();
            this.UnSetAllowUnsafeUpdates(webStatus);
        }

        /// <summary>
        /// Get DTO object
        /// </summary>
        /// <param name="item"></param>
        /// <returns>Entity</returns>
        protected abstract T MapDTO(SPListItem item);

        /// <summary>
        /// Repair data before add/update
        /// </summary>
        /// <param name="entity">DTO object</param>
        /// <returns>Dictionary collects Internal Field with Value to add/update</returns>
        protected Dictionary<string, object> GatherParameters(T entity)
        {
            if (entity == null) throw new Exception("Entity is null.");

            var di = new Dictionary<string, object>();
            foreach (PropertyInfo property in entity.GetType().GetProperties())
            {
                var prop = this.GetProperty(entity, property.Name);
                var propValue = prop.GetValue(entity, null);
                if (prop.PropertyType == typeof(LookupDTO))
                {
                    di.Add(property.Name, ((LookupDTO)propValue).ID);
                }
                else if (prop.PropertyType == typeof(IList<LookupDTO>))
                {
                    var list = propValue as IList<LookupDTO>;
                    if (list != null)
                    {
                        var valueCollection = new SPFieldLookupValueCollection();
                        valueCollection.AddRange(list.Select(lookup => new SPFieldLookupValue(lookup.ID, lookup.Title)));
                        di.Add(property.Name, valueCollection);
                    }
                    else di.Add(property.Name, null);
                }
                else
                    di.Add(property.Name, propValue);
            }
            return di;
        }

        #region Privates
        private SPList GetSPList()
        {
            SPList list = null;
            var relativeUrl = this.SPWeb.ServerRelativeUrl.Equals("/") ? string.Empty : this.SPWeb.ServerRelativeUrl;

            try
            {
                list = this.SPWeb.GetList(string.Concat(relativeUrl, "/Lists/", this.ListName));
            }
            catch
            {
                list = this.SPWeb.GetList(string.Concat(relativeUrl, "/", this.ListName));
            }

            return list;
        }

        private SPListItem GetSPListItem(int id)
        {
            var query = new SPQuery();
            query.Query = string.Format(@"<Where><Eq><FieldRef Name='ID'/><Value Type='Integer'>{0}</Value></Eq></Where>", id);
            query.RowLimit = 1;
            SPListItemCollection itemCollection = this.GetSPList().GetItems(query);
            if (itemCollection.Count > 0) return itemCollection[0];
            return null;
        }

        public SPListItemCollection GetSPListItemCollection(string query, string viewFields, string recursive, 
            int pageIndex, int pageSize)
        {
            var list = this.GetSPList();
            SPQuery spQuery = null;

            if (pageIndex < 2)
            {
                spQuery = new SPQuery()
                        {
                            Query = query,
                            RowLimit = (uint)pageSize,
                            ViewFields = viewFields
                        };
                if (!string.IsNullOrEmpty(recursive)) spQuery.ViewAttributes = recursive;
            }
            else
            {
                spQuery = new SPQuery()
                          {
                              Query = query,
                              RowLimit = (uint)((pageIndex - 1) * pageSize),
                              ViewFields = "<FieldRef Name='ID'/>",
                          };
                if (!string.IsNullOrEmpty(recursive)) spQuery.ViewAttributes = recursive;
                SPListItemCollection itemCollection = list.GetItems(spQuery);
                SPListItemCollectionPosition itemCollectionPosition = itemCollection.ListItemCollectionPosition;

                spQuery = new SPQuery()
                          {
                              Query = query,
                              RowLimit = (uint)pageSize,
                              ViewFields = viewFields
                          };
                if (!string.IsNullOrEmpty(recursive)) spQuery.ViewAttributes = recursive;

                if (itemCollectionPosition != null) spQuery.ListItemCollectionPosition = itemCollectionPosition;
            }
            return list.GetItems(spQuery);
        }

        private bool SetAllowUnsafeUpdates()
        {
            bool webStatus = false;

            if (!this.SPWeb.AllowUnsafeUpdates)
            {
                this.SPWeb.AllowUnsafeUpdates = true;
                webStatus = true;
            }

            return webStatus;
        }

        private void UnSetAllowUnsafeUpdates(bool webStatus)
        {
            if (webStatus)
            {
                this.SPWeb.AllowUnsafeUpdates = false;
            }
        }

        private PropertyInfo GetProperty(object entity, string propertyName)
        {
            if (entity.GetType().GetProperties().Count(p => p.Name == propertyName.Split('.')[0]) == 0)
                throw new ArgumentNullException(string.Format("Property {0}, is not exists in object {1}", propertyName,
                    entity.ToString()));
            if (propertyName.Split('.').Length == 1)
                return entity.GetType().GetProperty(propertyName);
            else
                return GetProperty(entity.GetType().GetProperty(propertyName.Split('.')[0]).GetValue(entity, null),
                    propertyName.Split('.')[1]);
        }
        #endregion
    }
}
